package javao.cn.work.orc.watch;


import javao.cn.work.orc.pojo.watch.PathConfig;
import javao.cn.work.orc.pojo.watch.WatchConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static java.nio.file.StandardWatchEventKinds.*;

/**
 * @Description 监听文件夹的工具类
 * @Author Admin小闫
 * @Date 2024/12/9
 */
@Component
@Slf4j
public class FileWatchComponent implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private WatchConfig watchConfig;

    @Autowired
    private WatchOrcService watchOrcService;

    private final ExecutorService executorService = Executors.newCachedThreadPool();
    private final Map<WatchService, Path> watchServices = new HashMap<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        for (PathConfig config : watchConfig.getWatchlist()) {
            Path dir = Paths.get(config.getWatchPath());
            if (Files.exists(dir) && Files.isDirectory(dir)) {
                try {
                    // 收集所有子目录
                    List<Path> allDirs = new ArrayList<>();
                    collectAllDirectories(dir, allDirs);
                    log.info("即将监听的目录及子目录: " + allDirs);

                    // 为每个目录启动监听任务
                    for (Path subDir : allDirs) {
                        executorService.submit(() -> {
                            try {
                                watchFile(subDir, config);
                            } catch (Exception e) {
                                log.error(e.getMessage(), e);
                            }
                        });
                    }
                } catch (IOException e) {
                    log.error("无法访问目录: " + dir, e);
                }
            } else {
                log.debug("指定路径不存在或不是目录: " + config.toString());
            }
        }
    }

    private void collectAllDirectories(Path dir, List<Path> allDirs) throws IOException {
        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path subDir, BasicFileAttributes attrs) throws IOException {
                allDirs.add(subDir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    private void watchFile(Path dir, PathConfig pathConfig) throws Exception {
        Map<Path,PathConfig> map=new HashMap<>();
        WatchService watchService = FileSystems.getDefault().newWatchService();
        watchServices.put(watchService, dir); // 维护 WatchService 和路径的关系

        // 注册目录以监听 ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY 事件
        dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        log.debug("开始监听 " + dir.toString() + " 的变化...");

        while (true) {
            // 等待一个新的事件
            WatchKey key;
            try {
                key = watchService.take(); // 阻塞直到有事件发生
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
                return; // 如果线程被中断，退出循环
            }

            for (WatchEvent<?> event : key.pollEvents()) {
                WatchEvent.Kind<?> kind = event.kind();

                if (kind == OVERFLOW) {
                    continue; // 如果是溢出事件，则跳过
                }

                @SuppressWarnings("unchecked")
                WatchEvent<Path> ev = (WatchEvent<Path>) event;
                Path fileName = ev.context();
                Path child = dir.resolve(fileName);

                if (kind == ENTRY_CREATE) {
                    log.debug("文件创建: " + child);

                    while (!isFileReady(child)){
                        Thread.sleep(1000);
                        log.warn("文件可能还在写入中-等待写入完成后重试: " + child);
                    }

                    // 处理新创建的文件或目录
                    if (Files.isDirectory(child)) {
                        // 如果是新创建的目录，也需要注册监听
                        log.debug("注册监听: " + child);
                        watchFile(child, pathConfig);
                    }

                } else if (kind == ENTRY_DELETE) {
                    // 处理被删除的文件
                    log.debug("文件已被删除: " + child);
                } else if (kind == ENTRY_MODIFY) {
                    log.debug("文件已被修改: " + child);
                    // 处理新创建的文件或目录
                    if (Files.isDirectory(child)) {
                        // 如果是新创建的目录，也需要注册监听
                        log.debug("注册监听: " + child);
                        watchFile(child, pathConfig);
                    }
                } else {
                    log.debug("其他操作: " + child);
                }
            }

            boolean valid = key.reset();
            if (!valid) {
                break; // 如果目录不再有效（例如被删除），则退出循环
            }

            // 使用 entrySet() 遍历
            for (Map.Entry<Path, PathConfig> entry : map.entrySet()) {
                Path path = entry.getKey();
                PathConfig value = entry.getValue();
                System.out.println(path.toFile().getName());
                watchOrcService.asynORCPdf(path, value);
            }

            map.clear();
        }
    }

    private boolean isFileReady(Path filePath) {
        File file = filePath.toFile();
        long lastSize = -1;
        long currentSize;

        for (int i = 0; i < 3; i++) { // 尝试3次
            currentSize = file.length();
            if (currentSize == lastSize) {
                return true; // 如果两次测量的大小相同，则认为文件已准备好
            }
            lastSize = currentSize;
            try {
                Thread.sleep(500); // 等待500毫秒
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        return false;
    }
}